#!/usr/bin/env python3
"""
星露谷Mod翻译脚本
使用DeepSeek API翻译manifest.json文件中的Name和Description字段
"""

import argparse
import json
import shutil
import sys
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import Dict, List, Optional, Tuple

import json5
import winreg
from openai import OpenAI
from tqdm import tqdm


def load_config() -> Dict:
    """加载配置文件。
    查找顺序：当前工作目录 -> 可执行文件所在目录（打包后一并支持）。
    """
    # 1) 当前工作目录
    cwd_path = Path("config.json")
    if cwd_path.exists():
        with open(cwd_path, "r", encoding="utf-8") as f:
            return json.load(f)

    # 2) 可执行文件所在目录（打包后）或源码所在目录
    if getattr(sys, "frozen", False):  # PyInstaller 打包
        base_dir = Path(sys.executable).parent
    else:
        base_dir = Path(__file__).parent
    exe_path = base_dir / "config.json"
    if exe_path.exists():
        with open(exe_path, "r", encoding="utf-8") as f:
            return json.load(f)

    raise FileNotFoundError("config.json not found")


def find_stardew_path() -> Optional[Path]:
    """从Windows注册表查找星露谷物语Steam版安装路径"""
    # Steam App ID: 413150
    registry_paths = [
        # 64位系统
        (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"),
        (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"),
        # 32位系统或用户安装
        (winreg.HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"),
    ]
    
    # 尝试查找 InstallLocation
    for hkey, path in registry_paths:
        try:
            key = winreg.OpenKey(hkey, path)
            try:
                install_location = winreg.QueryValueEx(key, "InstallLocation")[0]
                if install_location:
                    # 清理路径（可能包含引号或多余字符）
                    install_location = install_location.strip('"').strip()
                    stardew_path = Path(install_location)
                    if stardew_path.exists():
                        return stardew_path
            finally:
                winreg.CloseKey(key)
        except (OSError, FileNotFoundError):
            continue
    
    return None


def get_mods_directory(custom_path: Optional[str] = None) -> Path:
    """
    获取Mods目录路径
    
    优先级：
    1. 如果指定了 custom_path，使用指定路径
    2. 尝试从注册表查找Steam版安装路径
    3. 如果找不到，使用当前目录下的 Mods 目录
    """
    # 如果指定了自定义路径，优先使用
    if custom_path:
        return Path(custom_path)
    
    # 尝试从注册表查找Steam版安装路径
    stardew_path = find_stardew_path()
    if stardew_path:
        mods_path = stardew_path / "Mods"
        if mods_path.exists():
            return mods_path
    
    # 如果注册表找不到或路径不存在，使用当前目录
    return Path("./Mods")


def find_manifest_files(mods_dir: Path) -> List[Path]:
    """递归查找所有manifest.json文件"""
    return list(mods_dir.rglob("manifest.json"))


def is_already_translated(data: Dict) -> Tuple[bool, bool]:
    """检测Name和Description是否已包含中文"""
    def contains_chinese(text: str) -> bool:
        """检测字符串是否包含中文字符"""
        return any('\u4e00' <= char <= '\u9fff' for char in text)
    
    name_translated = False
    desc_translated = False
    
    if "Name" in data and data["Name"]:
        name_translated = contains_chinese(data["Name"])
    
    if "Description" in data and data["Description"]:
        desc_translated = contains_chinese(data["Description"])
    
    return name_translated, desc_translated


def get_cache_path() -> Path:
    """获取缓存文件路径（统一的单一缓存文件）"""
    return Path("./cache.json")


def load_cache() -> Dict:
    """加载统一的缓存文件"""
    cache_path = get_cache_path()
    if not cache_path.exists():
        return {}
    
    try:
        with open(cache_path, "r", encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        return {}


def save_cache(cache_data: Dict):
    """保存统一的缓存文件"""
    cache_path = get_cache_path()
    with open(cache_path, "w", encoding="utf-8") as f:
        json.dump(cache_data, f, ensure_ascii=False, indent=2)


def translate_text(
    client: OpenAI, 
    model: str, 
    text: str, 
    cache_key: str, 
    cache_data: Dict,
    cache_lock: threading.Lock
) -> str:
    """翻译单个文本"""
    # 检查缓存（需要加锁读取）
    with cache_lock:
        if cache_key in cache_data and text == cache_data[cache_key].get("original"):
            return cache_data[cache_key]["translated"]
    
    try:
        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": "你是一个专业的翻译助手，负责将英文翻译成简体中文。只返回翻译结果，不要添加任何解释或说明。"},
                {"role": "user", "content": f"请将以下文本翻译成简体中文：\n\n{text}"},
            ],
            stream=False,
            temperature=0.7
        )
        
        translated = response.choices[0].message.content.strip()
        
        # 更新缓存（需要加锁写入）
        with cache_lock:
            cache_data[cache_key] = {
                "original": text,
                "translated": translated
            }
        
        return translated
    except Exception as e:
        print(f"翻译失败: {e}")
        return text


def format_update_keys(update_keys: List[str]) -> str:
    """格式化UpdateKeys数组为字符串"""
    if not update_keys:
        return ""
    return ", ".join(update_keys)


def backup_manifest(manifest_path: Path) -> bool:
    """创建备份文件"""
    backup_path = manifest_path.with_suffix(".json.bak")
    if backup_path.exists():
        return False  # 备份已存在，跳过
    
    try:
        shutil.copy2(manifest_path, backup_path)
        return True
    except Exception as e:
        print(f"备份失败 {manifest_path}: {e}")
        return False


def process_manifest(
    manifest_path: Path,
    mods_dir: Path,
    client: OpenAI,
    model: str,
    cache_data: Dict,
    cache_lock: threading.Lock
) -> Tuple[bool, str, Optional[str]]:
    """
    处理单个manifest文件
    
    返回: (是否成功, 消息, 错误信息)
    """
    rel_path = manifest_path.relative_to(mods_dir)
    
    try:
        # 读取文件（使用json5支持注释和尾随逗号）
        with open(manifest_path, "r", encoding="utf-8") as f:
            content = f.read()
            data = json5.loads(content)
        
        # 检查是否已翻译
        name_translated, desc_translated = is_already_translated(data)
        
        # 获取manifest文件的相对路径作为缓存key前缀
        path_str = str(rel_path).replace("\\", "/")
        
        # 创建备份
        backup_created = backup_manifest(manifest_path)
        
        modified = False
        
        # 处理Name字段
        if "Name" in data and data["Name"] and not name_translated:
            original_name = data["Name"]
            cache_key = f"{path_str}::name"
            translated_name = translate_text(
                client, model, original_name, cache_key, cache_data, cache_lock
            )
            
            # 拼接Name: 中文-英文-[UpdateKeys]
            update_keys = data.get("UpdateKeys", [])
            # 过滤掉空字符串
            update_keys = [k for k in update_keys if k] if isinstance(update_keys, list) else []
            if update_keys:
                update_keys_str = format_update_keys(update_keys)
                data["Name"] = f"{translated_name}-{original_name}-[{update_keys_str}]"
            else:
                data["Name"] = f"{translated_name}-{original_name}"
            
            modified = True
        
        # 处理Description字段
        if "Description" in data and data["Description"] and not desc_translated:
            cache_key = f"{path_str}::description"
            translated_desc = translate_text(
                client, model, data["Description"], cache_key, cache_data, cache_lock
            )
            data["Description"] = translated_desc
            modified = True
        
        # 如果有修改，保存文件（使用标准JSON格式）
        if modified:
            with open(manifest_path, "w", encoding="utf-8") as f:
                json.dump(data, f, ensure_ascii=False, indent="\t")
            
            backup_status = "（已创建备份）" if backup_created else "（使用已有备份）"
            return True, f"✓ {rel_path} {backup_status}", None
        else:
            return True, f"- {rel_path} （已翻译或无需翻译）", None
    
    except Exception as e:
        error_msg = str(e)
        return False, f"✗ {rel_path} 错误: {error_msg}", error_msg


def restore_backups(mods_dir: Path):
    """恢复所有备份文件"""
    backup_files = list(mods_dir.rglob("manifest.json.bak"))
    
    if not backup_files:
        print("未找到备份文件")
        return
    
    print(f"找到 {len(backup_files)} 个备份文件\n")
    
    restored_count = 0
    failed_files = []
    
    # 使用进度条显示恢复进度
    with tqdm(total=len(backup_files), desc="恢复备份", unit="文件") as pbar:
        for backup_path in backup_files:
            # 移除.bak后缀，恢复为manifest.json
            manifest_path = backup_path.parent / backup_path.stem
            try:
                shutil.copy2(backup_path, manifest_path)
                restored_count += 1
                pbar.set_postfix({"状态": "成功"})
                failed_files.append(None)
            except Exception as e:
                error_msg = f"{manifest_path.relative_to(mods_dir)}: {e}"
                failed_files.append(error_msg)
                pbar.set_postfix({"状态": "失败"})
            
            pbar.update(1)
    
    # 删除缓存文件
    cache_path = get_cache_path()
    if cache_path.exists():
        cache_path.unlink()
        print(f"\n✓ 已删除缓存文件")
    
    # 显示统计信息
    failed_count = sum(1 for f in failed_files if f is not None)
    print(f"\n{'='*60}")
    print(f"恢复完成！")
    print(f"总计: {len(backup_files)} 个备份文件")
    print(f"  ✓ 成功恢复: {restored_count} 个")
    if failed_count > 0:
        print(f"  ✗ 恢复失败: {failed_count} 个")
    print(f"{'='*60}")
    
    # 显示失败列表
    if failed_count > 0:
        print(f"\n失败的文件列表:")
        print(f"{'-'*60}")
        for error_msg in failed_files:
            if error_msg:
                print(f"  ✗ {error_msg}")
        print(f"{'-'*60}")


def main():
    parser = argparse.ArgumentParser(description="星露谷Mod翻译脚本")
    parser.add_argument(
        "--path",
        type=str,
        default=None,
        help="指定Mods目录路径（如果不指定，将先尝试从注册表查找Steam版路径，找不到则使用当前目录）"
    )
    parser.add_argument(
        "--restore",
        action="store_true",
        help="恢复所有备份文件并删除缓存"
    )
    
    args = parser.parse_args()
    
    # 获取Mods目录
    mods_dir = get_mods_directory(args.path)
    
    # 恢复模式
    if args.restore:
        if not mods_dir.exists():
            print(f"Mods目录不存在: {mods_dir}")
            sys.exit(1)
        restore_backups(mods_dir)
        return
    
    # 显示使用的Mods目录
    if args.path:
        print(f"使用指定路径: {mods_dir}")
    else:
        stardew_path = find_stardew_path()
        if stardew_path and (stardew_path / "Mods").exists():
            print(f"从注册表找到Steam版路径: {mods_dir}")
        else:
            print(f"使用当前目录: {mods_dir}")
    
    if not mods_dir.exists():
        print(f"错误: Mods目录不存在: {mods_dir}")
        sys.exit(1)
    
    # 加载配置
    try:
        config = load_config()
    except FileNotFoundError as e:
        print(f"错误: {e}")
        sys.exit(1)
    
    # 查找所有manifest文件
    manifest_files = find_manifest_files(mods_dir)
    if not manifest_files:
        print(f"未找到manifest.json文件")
        sys.exit(0)
    
    print(f"找到 {len(manifest_files)} 个manifest.json文件")
    print(f"使用并发数: {config['max_concurrent']}\n")
    
    # 加载统一缓存
    cache_data = load_cache()
    cache_lock = threading.Lock()
    
    # 创建OpenAI客户端
    client = OpenAI(
        api_key=config["api_key"],
        base_url=config["base_url"]
    )
    
    # 使用线程池并行处理，显示进度条
    results = []
    failed_files = []
    
    with ThreadPoolExecutor(max_workers=config["max_concurrent"]) as executor:
        # 提交所有任务
        futures = {
            executor.submit(process_manifest, mf, mods_dir, client, config["model"], cache_data, cache_lock): mf
            for mf in manifest_files
        }
        
        # 使用tqdm显示进度条
        with tqdm(total=len(manifest_files), desc="翻译进度", unit="文件") as pbar:
            for future in as_completed(futures):
                manifest_path = futures[future]
                success, message, error_msg = future.result()
                
                results.append((success, message))
                
                # 更新进度条
                if success:
                    pbar.set_postfix({"状态": "成功"})
                else:
                    pbar.set_postfix({"状态": "失败"})
                    failed_files.append((manifest_path, error_msg))
                
                pbar.update(1)
    
    # 保存统一缓存
    save_cache(cache_data)
    
    # 统计结果
    modified_count = sum(1 for s, msg in results if s and "✓" in msg)
    skipped_count = sum(1 for s, msg in results if s and "-" in msg)
    failed_count = len(failed_files)
    total_count = len(results)
    
    # 显示统计信息
    print(f"\n{'='*60}")
    print(f"处理完成！")
    print(f"总计: {total_count} 个文件")
    print(f"  ✓ 已修改: {modified_count} 个")
    print(f"  - 已跳过: {skipped_count} 个（已翻译或无需翻译）")
    if failed_count > 0:
        print(f"  ✗ 失败: {failed_count} 个")
    print(f"{'='*60}")
    
    # 显示失败列表
    if failed_files:
        print(f"\n失败的文件列表:")
        print(f"{'-'*60}")
        for manifest_path, error_msg in failed_files:
            rel_path = manifest_path.relative_to(mods_dir)
            print(f"  ✗ {rel_path}")
            print(f"    错误: {error_msg}")
        print(f"{'-'*60}")


if __name__ == "__main__":
    main()

